#include <bits/stdc++.h>
using i64 = int_fast64_t;
#define repeat(i, a, b) for(int i = (a); (i) < (b); ++(i))
#define rep(i, n) repeat(i, 0, n)

namespace edamat {
template <class Info>
struct edge {
    int from, to;
    Info info;
    edge(int to, Info info) : to(to), info(info) {}
    edge(int from, int to, Info info) : from(from), to(to), info(info) {}
};

struct normal_edge {
    normal_edge() {}
};

//! graphは基底クラス！使うときはundirected_graphかdirected_graphを使う。
template <class Info>
class graph {
   protected:
    using edge_type = edge<Info>;
    ::std::size_t N;
    ::std::vector<::std::vector<edge_type>> edges;

   public:
    graph(::std::size_t N_) : N(N_), edges(N_) {}
    virtual void add_edge(int x, int y, Info info = static_cast<Info>(1)) = 0;
    const ::std::vector<edge_type> &delta(int v) const {
        assert(v < N);
        return edges[v];
    }
    const ::std::vector<edge_type> &operator[](int v) const {
        return this->delta(v);
    }
    ::std::size_t size() const noexcept { return N; }
    ::std::vector<Info> bfs(int);
    ::std::vector<Info> dijkstra(int);
    ::std::vector<Info> bellman_ford(int);
    ::std::vector<::std::vector<Info>> warshall_floyd(void);
};

template <class Info>
class undirected_graph : public graph<Info> {
   public:
    undirected_graph(::std::size_t N) : graph<Info>(N) {}
    void add_edge(int x, int y, Info info = static_cast<Info>(1)) {
        this->edges[x].push_back(edge<Info>(x, y, info));
        this->edges[y].push_back(edge<Info>(y, x, info));
    }
};

template <class Info>
class directed_graph : public graph<Info> {
   public:
    directed_graph(::std::size_t N) : graph<Info>(N) {}
    void add_edge(int x, int y, Info info = static_cast<Info>(1)) {
        this->edges[x].push_back(edge<Info>(x, y, info));
    }
    ::std::vector<int> topological_sort(void);
};

template <class Info>
class grid_graph : public graph<Info> {
    // グリッドの座標 (i,j) に対し頂点番号 i*W+j を対応させる
    // ことで，グリッドを普通のグラフのように扱う
   public:
    ::std::size_t H, W;
    grid_graph(::std::size_t H, ::std::size_t W) : graph<Info>(H * W), H(H), W(W) {}
    void add_edge(int x, int y, Info info = static_cast<Info>(1)) {
        this->edges[x].push_back(edge<Info>(x, y, info));
    }
    void add_edge(int i, int j, int ni, int nj, Info info = static_cast<Info>(1)) {
        add_edge(to_int(i, j), to_int(ni, nj), info);
    }
    inline int to_int(int i, int j) {
        return i * W + j;
    };
    inline std::pair<int, int> to_grid(int n) {
        return std::pair<int, int>{n / W, n % W};
    };
    void make_graph(std::vector<std::vector<bool>> &A) {
        for(int i = 0; i < H; ++i) {
            for(int j = 0; j < W; ++j) {
                for(int di = -1; di <= 1; ++di) {
                    for(int dj = -1; dj <= 1; ++dj) {
                        if(abs(di) + abs(dj) == 1) {
                            int ni = i + di, nj = j + dj;
                            if(ni < 0 or ni >= H or nj < 0 or nj >= W) {
                                continue;
                            }
                            if(A[i][j] and A[ni][nj]) {
                                add_edge(i, j, ni, nj);
                                add_edge(ni, nj, i, j);
                            }
                        }
                    }
                }
            }
        }
    }
};

template <typename Info>
::std::vector<Info> graph<Info>::bfs(int s) {
    const auto INF = ::std::numeric_limits<Info>::max();
    ::std::vector<Info> dist(edges.size(), INF);
    ::std::vector<int> b(edges.size(), -1);  // 経路復元

    ::std::queue<Info> q;
    q.push(s);
    dist[s] = 0;
    while(!q.empty()) {
        Info v = q.front();
        q.pop();
        for(auto e : edges[v]) {
            if(dist[e.to] != INF) continue;
            dist[e.to] = dist[e.from] + e.info;
            b[e.to] = e.from;  // 経路復元
            q.push(e.to);
        }
    };
    return dist;
}

template <typename Info>
::std::vector<Info> graph<Info>::dijkstra(int s) {
    const auto INF = ::std::numeric_limits<Info>::max();
    ::std::vector<Info> dist(edges.size(), INF);
    ::std::vector<int> b(edges.size(), -1);  // 経路復元
    using Pi = ::std::pair<Info, int>;

    ::std::priority_queue<Pi, std::vector<Pi>, std::greater<Pi>> que;
    dist[s] = 0;
    que.emplace(dist[s], s);
    while(!que.empty()) {
        Info cost;
        int idx;
        ::std::tie(cost, idx) = que.top();
        que.pop();
        if(dist[idx] < cost) continue;
        for(auto &e : edges[idx]) {
            auto next_cost = cost + e.info;
            if(dist[e.to] <= next_cost) continue;
            dist[e.to] = next_cost;
            b[e.to] = idx;  // 経路復元
            que.emplace(dist[e.to], e.to);
        }
    }
    return dist;
};

template <typename Info>
::std::vector<Info> graph<Info>::bellman_ford(int s) {
    const auto INF = ::std::numeric_limits<Info>::max();
    ::std::vector<Info> dist(edges.size(), INF);
    ::std::vector<int> b(edges.size(), -1);  // 経路復元

    dist[s] = 0;
    for(int i = 0; i < edges.size() - 1; i++) {
        for(int idx = 0; idx < edges.size(); idx++) {
            for(auto &e : edges[idx]) {
                if(dist[e.from] == INF) continue;
                auto next_cost = dist[e.from] + e.info;
                if(dist[e.to] <= next_cost) continue;
                dist[e.to] = next_cost;
                b[e.to] = e.from;  // 経路復元
            }
        }
    }
    for(int idx = 0; idx < edges.size(); idx++) {
        for(auto &e : edges[idx]) {
            if(dist[e.from] == INF) continue;
            if(dist[e.from] + e.info < dist[e.to]) return ::std::vector<Info>();
        }
    }
    // TODO:負閉路があったときに，影響のある頂点を返せるようにする
    return dist;
}

template <typename Info>
::std::vector<::std::vector<Info>> graph<Info>::warshall_floyd() {
    const auto INF = ::std::numeric_limits<Info>::max();
    ::std::vector<::std::vector<Info>> dist(edges.size(), ::std::vector<Info>(edges.size(), INF));

    for(int idx = 0; idx < edges.size(); idx++) dist[idx][idx] = static_cast<Info>(0);

    for(int idx = 0; idx < edges.size(); idx++) {
        for(auto &e : edges[idx]) {
            dist[e.from][e.to] = e.info;
        }
    }
    for(int k = 0; k < edges.size(); k++) {
        for(int i = 0; i < edges.size(); i++) {
            for(int j = 0; j < edges.size(); j++) {
                if(dist[i][k] == INF or dist[k][j] == INF) continue;
                dist[i][j] = ::std::min<Info>(dist[i][j], dist[i][k] + dist[k][j]);
            }
        }
    }
    // dist[i][i] !=0 となる i があるとき負閉路が存在する
    return dist;
}

template <typename Info>
::std::vector<int> directed_graph<Info>::topological_sort() {
    ::std::vector<int> h(this->size(), 0);
    for(int idx = 0; idx < this->size(); idx++) {
        for(auto &e : this->edges[idx]) {
            h[e.to]++;
        }
    }

    ::std::stack<int> st;
    rep(i, this->size()) if(h[i] == 0) st.push(i);
    ::std::vector<int> ret;
    while(st.size()) {
        int i = st.top();
        st.pop();
        ret.emplace_back(i);
        for(auto &e : this->edges[i]) {
            h[e.to]--;
            if(h[e.to] == 0) st.push(e.to);
        }
    }
    return ret;
}
}  // namespace edamat

void solve() {
    int H, W;
    std::cin >> H >> W;
    std::vector<std::vector<bool>> A(H, std::vector<bool>(W));
    rep(h, H) {
        std::string s;
        std::cin >> s;
        rep(w, W) {
            A[h][w] = (s[w] == '.');
        }
    }
    edamat::grid_graph<int> g(H, W);
    g.make_graph(A);
    int ans = -1;
    auto d = g.warshall_floyd();
    rep(i, H * W) {
        for(auto x : d[i]) {
            if(x >= ans and x != std::numeric_limits<int>::max()) {
                ans = x;
            }
        }
    }
    std::cout << ans << std::endl;
}

int main() {
    // std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    solve();
    return 0;
}

Measured memory_bytes: 1024